import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                             QHBoxLayout, QLabel, QPushButton, QMessageBox)
from PyQt5.QtCore import Qt, QPointF, QLineF  # 添加 QLineF 导入
from PyQt5.QtGui import QFont, QPainter, QPen, QColor, QBrush, QPainterPath
import random

class PianoKey(QPushButton):
    def __init__(self, note, is_black=False, parent=None):
        super().__init__(parent)
        self.note = note
        self.is_black = is_black
        
        # 设置键的尺寸
        if is_black:
            self.setFixedSize(23, 120)
        else:
            self.setFixedSize(38, 200)
        
        # 设置颜色
        self.normal_color = "#000000" if is_black else "#FFFFFF"
        self.pressed_color = "#333333" if is_black else "#DDDDDD"
        self.highlight_color = "#4CAF50"
        self.wrong_color = "#FF5733"
        
        self.update_style()
        
        # 显示音符标签
        self.label = QLabel(note, self)
        self.label.setAlignment(Qt.AlignCenter)
        font = QFont("Arial", 8 if is_black else 10)
        font.setBold(True)
        self.label.setFont(font)
        self.label.setStyleSheet("color: white;" if is_black else "color: black;")
        
        if is_black:
            self.label.setGeometry(0, 80, 23, 30)
        else:
            self.label.setGeometry(0, 160, 38, 30)
            
        self.label.hide()
    
    def update_style(self):
        self.setStyleSheet(f"""
            QPushButton {{
                background-color: {self.normal_color};
                border: 1px solid #888888;
                border-radius: 0px;
            }}
            QPushButton:pressed {{
                background-color: {self.pressed_color};
            }}
        """)
    
    def highlight(self, correct=False):
        color = self.highlight_color if correct else self.wrong_color
        self.setStyleSheet(f"""
            QPushButton {{
                background-color: {color};
                border: 1px solid #000000;
                border-radius: 0px;
            }}
        """)

class PianoWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.white_keys = []
        self.black_keys = []
        self.setFixedHeight(220)
    
    def resizeEvent(self, event):
        for black_key in self.black_keys:
            index = black_key.white_key_index
            if 0 <= index < len(self.white_keys):
                white_key = self.white_keys[index]
                black_key.move(white_key.pos().x() + white_key.width() - black_key.width()//2 - 1, 
                               white_key.pos().y())
        super().resizeEvent(event)

class StaffWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(400, 250)  # 增大Widget尺寸
        self.note = ""
        self.clef = "treble"
        
    def set_note(self, note):
        self.note = note
        octave = int(note[-1]) if note[-1].isdigit() else 0
        note_name = note[:-1] if note[-1].isdigit() else note
        
        if octave > 3 or (octave == 3 and note_name in ['B', 'C']):
            self.clef = "treble"
        else:
            self.clef = "bass"
        self.update()
    
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 绘制背景
        painter.fillRect(self.rect(), QColor(255, 255, 255))
        
        # 五线谱参数
        staff_top = 80  # 下移五线谱位置
        line_spacing = 12
        staff_left = 80
        staff_width = 240
        center_x = staff_left + staff_width // 2
        
        # 绘制五线谱
        pen = QPen(Qt.black, 2)
        painter.setPen(pen)
        
        for i in range(5):
            y = staff_top + i * line_spacing
            painter.drawLine(QLineF(staff_left, y, staff_left + staff_width, y))
        
        # 绘制谱号
        if self.clef == "treble":
            self.draw_treble_clef(painter, staff_left - 40, staff_top - 20, line_spacing)
        else:
            self.draw_bass_clef(painter, staff_left - 30, staff_top - 10, line_spacing)
        
        if not self.note:
            return
        
        # 计算音符位置
        note_name = self.note[:-1] if self.note[-1].isdigit() else self.note
        octave = int(self.note[-1]) if self.note[-1].isdigit() else 0
        
        # 音符位置计算
        if self.clef == "treble":
            base_pos = 6  # 高音谱号下加一线(C4)
            note_offsets = {
                "C": 0, "D": 0.5, "E": 1, "F": 1.5, "G": 2, "A": 2.5, "B": 3,
                "C#": 0, "Db": 0, "D#": 0.5, "Eb": 0.5, "F#": 1.5, "Gb": 1.5,
                "G#": 2, "Ab": 2, "A#": 2.5, "Bb": 2.5
            }
        else:
            base_pos = 2  # 低音谱号上加一线(C4)
            note_offsets = {
                "C": 0, "D": 0.5, "E": 1, "F": 1.5, "G": 2, "A": 2.5, "B": 3,
                "C#": 0, "Db": 0, "D#": 0.5, "Eb": 0.5, "F#": 1.5, "Gb": 1.5,
                "G#": 2, "Ab": 2, "A#": 2.5, "Bb": 2.5
            }
        
        if note_name in note_offsets:
            # 计算相对于中央C的位置
            if self.clef == "treble":
                octave_diff = octave - 4
            else:
                octave_diff = octave - 3
            
            position = base_pos + note_offsets[note_name] + octave_diff * 3.5
            note_y = staff_top + (4 - position) * line_spacing / 2
            
            # 确保音符不会超出widget边界
            note_radius = line_spacing * 0.6
            min_y = note_radius + 10  # 上边距
            max_y = self.height() - note_radius - 10  # 下边距
            note_y = max(min_y, min(note_y, max_y))
            
            # 绘制音符
            self.draw_note(painter, center_x, note_y, line_spacing)
            
            # 绘制加线
            self.draw_ledger_lines(painter, center_x, note_y, staff_top, line_spacing, staff_width)
    
    def draw_treble_clef(self, painter, x, y, line_spacing):
        path = QPainterPath()
        path.moveTo(x + 10, y + line_spacing * 4)
        path.lineTo(x + 5, y + line_spacing * 3)
        path.lineTo(x + 5, y + line_spacing * 1)
        path.lineTo(x + 10, y)
        path.lineTo(x + 15, y + line_spacing * 1)
        path.lineTo(x + 15, y + line_spacing * 3)
        path.lineTo(x + 10, y + line_spacing * 4)
        
        path.moveTo(x + 15, y + line_spacing * 3)
        path.lineTo(x + 20, y + line_spacing * 3.5)
        path.lineTo(x + 15, y + line_spacing * 4)
        
        painter.setBrush(QBrush(Qt.black))
        painter.drawPath(path)
    
    def draw_bass_clef(self, painter, x, y, line_spacing):
        path = QPainterPath()
        path.moveTo(x + 10, y + line_spacing * 0.5)
        path.addEllipse(x + 5, y + line_spacing * 0.5, 10, 10)
        
        path.moveTo(x + 10, y + line_spacing * 2)
        path.addEllipse(x + 5, y + line_spacing * 2, 10, 10)
        
        path.moveTo(x + 10, y + line_spacing * 4)
        path.lineTo(x + 10, y + line_spacing * 0.5)
        
        painter.setBrush(QBrush(Qt.black))
        painter.drawPath(path)
    
    def draw_note(self, painter, x, y, line_spacing):
        # 只绘制符头
        note_radius = line_spacing * 0.6
        painter.setBrush(QBrush(Qt.black))
        painter.drawEllipse(QPointF(x, y), note_radius, note_radius * 0.8)
    
    def draw_ledger_lines(self, painter, x, y, staff_top, line_spacing, staff_width):
        # 绘制加线（仅绘制与五线谱间隔相同的线）
        staff_bottom = staff_top + line_spacing * 4
        line_length = line_spacing * 3
        
        # 检查是否需要在上方加线（仅当音符位于五线谱上方超过一个线间距时）
        if y <= staff_top - line_spacing:
            # 计算需要多少条完整的加线（间隔与五线谱相同）
            lines_above = int((staff_top - y) // line_spacing) + 1
            for i in range(lines_above):
                line_y = staff_top - (i + 1) * line_spacing
                # 确保加线不会太靠近widget边缘
                if line_y >= 10:
                    painter.drawLine(QLineF(x - line_length/2, line_y, x + line_length/2, line_y))
        
        # 检查是否需要在下文加线（仅当音符位于五线谱下方超过一个线间距时）
        elif y >= staff_bottom + line_spacing:
            # 计算需要多少条完整的加线（间隔与五线谱相同）
            lines_below = int((y - staff_bottom) // line_spacing) + 1
            for i in range(lines_below):
                line_y = staff_bottom + (i + 1) * line_spacing
                # 确保加线不会太靠近widget边缘
                if line_y <= self.height() - 10:
                    painter.drawLine(QLineF(x - line_length/2, line_y, x + line_length/2, line_y))

class PianoTrainer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("钢琴键位学习助手")
        self.setGeometry(100, 100, 1000, 500)
        
        self.current_note = ""
        self.score = 0
        self.attempts = 0
        self.keys = {}
        
        self.initUI()
        self.generate_random_note()
    
    def initUI(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        main_layout = QVBoxLayout()
        central_widget.setLayout(main_layout)
        
        # 信息显示区域
        info_layout = QHBoxLayout()
        self.staff_widget = StaffWidget()
        self.score_label = QLabel("得分: 0/0")
        self.score_label.setFont(QFont("Arial", 16))
        
        info_layout.addWidget(self.staff_widget)
        info_layout.addStretch()
        info_layout.addWidget(self.score_label)
        main_layout.addLayout(info_layout)

        # 钢琴键盘区域
        self.piano_widget = PianoWidget()
        self.piano_layout = QHBoxLayout(self.piano_widget)
        self.piano_layout.setSpacing(0)
        self.piano_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.addWidget(self.piano_widget)

        # 创建钢琴键盘
        self.create_piano_keys()
        
        # 控制按钮
        control_layout = QHBoxLayout()
        new_note_btn = QPushButton("新音符")
        new_note_btn.clicked.connect(self.generate_random_note)
        show_keys_btn = QPushButton("显示所有键位")
        show_keys_btn.clicked.connect(self.toggle_key_labels)
        reset_btn = QPushButton("重置分数")
        reset_btn.clicked.connect(self.reset_score)
        
        control_layout.addWidget(new_note_btn)
        control_layout.addWidget(show_keys_btn)
        control_layout.addWidget(reset_btn)
        main_layout.addLayout(control_layout)
    
    def create_piano_keys(self):
        # 创建白键
        white_notes = []
        octaves = [0, 1, 2, 3, 4, 5, 6, 7, 8]
        white_keys = ['C', 'D', 'E', 'F', 'G', 'A', 'B']

        for octave in octaves:
            for key in white_keys:
                if octave == 0 and key not in ['A', 'B']:
                    continue
                if octave == 8 and key != 'C':
                    continue
                white_notes.append(f"{key}{octave}")
        
        for i, note in enumerate(white_notes):
            key = PianoKey(note, False, self.piano_widget)
            key.clicked.connect(self.key_clicked)
            self.piano_layout.addWidget(key)
            self.piano_widget.white_keys.append(key)
            self.keys[note] = key
        
        # 创建黑键
        black_notes = []
        black_positions = []
        
        for octave in range(0, 8):
            black_notes.append(f"A#{octave}")
            black_positions.append(0 + (octave)*7)
            
            if octave < 7:
                black_notes.append(f"C#{octave+1}")
                black_positions.append(2 + (octave)*7)
                black_notes.append(f"D#{octave+1}")
                black_positions.append(3 + (octave)*7)
                black_notes.append(f"F#{octave+1}")
                black_positions.append(5 + (octave)*7)
                black_notes.append(f"G#{octave+1}")
                black_positions.append(6 + (octave)*7)
        
        # 过滤不存在的黑键
        valid_black_notes = []
        valid_positions = []
        for note, pos in zip(black_notes, black_positions):
            if pos < len(self.piano_widget.white_keys):
                valid_black_notes.append(note)
                valid_positions.append(pos)
        
        for note, pos in zip(valid_black_notes, valid_positions):
            key = PianoKey(note, True, self.piano_widget)
            key.clicked.connect(self.key_clicked)
            key.white_key_index = pos
            self.piano_widget.black_keys.append(key)
            self.keys[note] = key
        
        # 调整钢琴键盘总宽度
        total_width = len(self.piano_widget.white_keys) * 38
        self.piano_widget.setFixedWidth(total_width)
    
    def key_clicked(self):
        sender = self.sender()
        selected_note = sender.note
        
        if selected_note == self.current_note:
            self.score += 1
            sender.highlight(correct=True)
            QMessageBox.information(self, "正确", f"恭喜你！{self.current_note}是正确的！")
        else:
            if self.current_note in self.keys:
                self.keys[self.current_note].highlight(correct=True)
            sender.highlight(correct=False)
            QMessageBox.warning(self, "错误", 
                              f"不正确。你点击了 {selected_note}，但正确答案是 {self.current_note}。")
        
        self.attempts += 1
        self.update_score()
        self.generate_random_note()
    
    def generate_random_note(self):
        for key in self.keys.values():
            key.update_style()
            key.label.hide()
        
        available_notes = list(self.keys.keys())
        self.current_note = random.choice(available_notes)
        self.staff_widget.set_note(self.current_note)
    
    def toggle_key_labels(self):
        for key in self.keys.values():
            if key.label.isVisible():
                key.label.hide()
            else:
                key.label.show()
    
    def update_score(self):
        percentage = int(self.score/self.attempts*100) if self.attempts > 0 else 0
        self.score_label.setText(f"得分: {self.score}/{self.attempts} ({percentage}%)")
    
    def reset_score(self):
        self.score = 0
        self.attempts = 0
        self.update_score()
        self.generate_random_note()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    trainer = PianoTrainer()
    trainer.show()
    sys.exit(app.exec_())